/** ============================================================================================ **/
/** 	Definizione della direttiva "Visualizzazione su mappa del risultato della ricerca" ...	 **/
/** ============================================================================================ **/

 var ngTemplateRicercaOnMap = 
	 		"<div class=\"widgetRicercaOnMap nop {{isLoading ? 'loading-table' : ''}}\" >" +

	 			/* mappa */
	 			"<div id='mapRisTb' class='map mapricerca' ngeo-map='olMapTb' tabindex='0' ></div>" + 
	 			
			"</div>";
 
/**
 * USO:
 *  È necessario includere nella pagina, in cui si vuole rendere disponibile la mappa per la geolocalizzazione degli elemnti 
 *  di ricerca e l'apertura di una popup informativa al click della feature, la seguente direttiva:
 *  
 *   (code)
 *   	<widget-ricerca-on-map api="..." config="..." parent-scope="..."></widget-ricerca-on-map>
 *   (code)
 *   
 * COSA FA:
 *   La direttiva crea il widget per visualizzare su mappa il risultato della chiamata al servizio di ricerca.
 *   Il widget è costituito dai seguenti componenti:
 *     1. mappa
 *   
 *  Gli attributi necessari alla direttiva sono:
 *  
 *       ATTRIBUTO 	  |   TYPE 	 | REQUIRED |  	DESCRIZIONE
 *---------------------------------------------------------------------------------------------------------------------------------------------------------------------------  
 *  	 api	  	  |  Object  |    --    | Valorizzato dalla direttiva con le funzioni invocabili(accessibili) da un controller esterno
 *    parentScope 	  |  Object  |    SI    | Scope del controller che usa la direttiva
 *    	config	  	  |  Object  |    SI    | Configurazione necessarie per il build del widget
 * enableStrategyBbox |  Boolean |    NO	| Determina la strategy di caricamento da usare per il layer vector che accoglie le fetaures risultanti 
 * 					  |			 |			| dalla ricerca. 
 * 					  |			 |			| Il valore predefinito è False, il layer è configurato con strategy "ol.loadingstrategy.all", ciò 
 * 					  |			 | 			| significa che carica tutte le features contemporaneamente in una sola chiamata.
 * 					  |			 |			| Al contrario, il layer è configurato con strategy "ol.loadingstrategy.bbox", ciò significa che carica
 * 					  | 		 |  		| le features sulla base dell'estensione della view della mappa. 
 * 					  | 		 |			| Tale valore viene fornito al server nel parametro "envelope", il quale è del seguente tipo: 
 * 				      |			 |		    | 	Object{
 * 				      |			 |			| 		double minx, the minimum x-coordinate
 * 				      |			 |			|   	double maxx, the maximum x-coordinate
 * 				      |			 |			|   	double miny, the minimum y-coordinate
 * 				      |			 |			| 		double max, the maximum y-coordinate
 * 				      |			 |			| 	}
 * 					  |			 |			| Quando la view cambia, la chiamata al server cosi come è configurata viene ripetuta fornendo un nuovo
 * 					  |			 | 			| valore per il parametro di request "envelope".
 *  				  |			 |			|
 * 
 * 
 *  L'attributo "config" è un oggetto composto dalle seguenti proprietà:
 *  
 *     ATTRIBUTO    |   TYPE   | REQUIRED |  DESCRIZIONE
 *----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 *   requestData	|  Object  |    SI 	  | Oggetto valorizzato con i dati con cui filtrare la ricerca.
 *   layerName		|  String  |    SI	  | Nome del layer dell'anagrafica, necessario per la definizione dello stile. L'elenco dei nome layer 
 *  				|		   |	      |	è definito nel file delle constanti(StyleConstants.js) col nome "Constants.layersName".
 *	 url			|  String  | 	SI    | Url del servizio di ricerca 
 *
 *  templatePopup	|  String  |    SI	  | Alla selezione della feature viene aperta una popup dove sono mostrate le informazioni dell'anagrafica.
 *					|		   |		  | La presentazione e le informazioni da mostrare sono determinate dal template che fornite al widget.
 *					|		   |		  | Considerato che nel formato GeoJson i dati custom della feature sono scritti nella propeietà "properties",
 *					|		   |		  | nel template l'accesso ai dati sarà "properties.<...>".
 *					|		   |		  |					
 *					|		   |		  |   Esempio:
 *					|		   |		  | <li><strong>{{'header.label.id' | translate}}</strong> {{properties.id}}</li>
 *					|		   | 		  | <li><strong>{{'header.label.nome' | translate}}</strong> {{properties.nome}}</li>
 *					|		   |		  | 
 *					|		   |          | L'accesso alle anagrafiche da mappa, deve dare la possibilità come per la tabella di andare nel 
 *				    |		   |		  | dettaglio, eliminare, ecc...
 *					|		   |		  | L'azione sul click del button è impostata nel seguente modo "parentScope.<....>()".
 *					|		   |		  |  
 *					|		   |		  |  Esempio:
 *					|		   |		  |  	<button type='button' class='btn-trasp' title="{{'button.dettaglio.title' | translate}}"
 *					|		   |		  |  		ng-click="parentScope.gotoDettaglio(properties.id)" ><i class='fa fa-arrow-right'></i>
 *					|		   |		  | 	</button>
 *  				|		   |		  |
 *    
 * NOTA:
 *   Il servizio di ricerca deve essere una POST, la risposta del servizio deve essere l'oggetto @link ResponseData, la lista di elementi deve
 *   essere nel fomato GeoJson quindi una @link FeatureCollection.
 *   Il widget ha un sistema interno di gestione del loading.
 *    
 *  La direttiva mette a disposizione le seguenti funzioni accessibili da un controller esterno:
 *  
 *     		FUNCTION    	   |  DESCRIZIONE
 *----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 *        "updateSize" 		   | Forza il ricalcolo della dimensione della mappa. Questo dovrebbe essere chiamato quando il codice di terze parti 
 *   			   			   | cambia la dimensione della finestra in cui è disegnata la mappa.
 *   			    		   |
 *        "refresh"            | Ripete la ricerca con i nuovi filtri impostati, in seguito alla ricerca, se non attiva l'opzione "enableStrategyBbox",
 *   			   			   | sposta la vista della mappa ad un estensione ed uno zoom tale che i marker siano visibili.
 *   			   			   |
 *       "selectFeature" 	   | NOTA:
 * 				   		       |   La seguente funzione è specifica per quando la direttiva è configurata perchè il caricamento delle feature avvenga
 *  					       |   utilizzando la strategy "BBOX".
 * 				   		       | -------------------------------------------------------------------------------------------------------------------
 * 							   | Evidenzia la fetaure le cui proprietà hanno la chiave di attributo fornita impostato al valore dato, se la feature 
 *   			  		 	   | viene individuata ne apre anche la popup.
 * 				  	 		   |  Parametri:
 * 				   			   | 	{@link String}  key, nome dell'attributo     (OBB)
 * 				      		   |	{@link Mixed} value, valore dell'attributo   (OBB)
 * 				   		       |
 * 		"selectFeatureBbox"    | NOTA:
 * 				   		       |   La seguente funzione è specifica per quando la direttiva è configurata perchè il caricamento delle feature avvenga
 *  					       |   utilizzando la strategy "BBOX".
 * 				   		       | -------------------------------------------------------------------------------------------------------------------
 * 				   		       | Evidenzia la fetaure le cui proprietà hanno la chiave di attributo fornita impostato al valore dato,
 * 				   		       | se la feature viene individuata ne apre la popup.
 * 				   		       |  Parametri:
 * 				   		       |  	 {@link String} key, nome dell'attributo			(OBB)
 * 				   		       |	 {@link Mixed} value, valore dell'attributo			(OBB)
 * 				   		       |	 {@link String} geomAsWkt, rappresentazione WKT della geometria della feature da evidenziare		(OBB)
 * 							   |
 * "getPropertiesByFeatureId"  | Ritorna le proprietà della feature con id "properties.id" uguale al valore fornito in ingresso alla funzione, 
 *   						   | altrimenti ritorna null.
 *    						   |  Parametri:
 *    						   |  	 {@link String} id, Identificativo elemento    (OBB)
 * 				   		       |
 *"updatePropertiesByFeatureId"| Aggiorna le proprietà e/o la geometria della feature e della popup con id "properties.id".
 * 							   |  Parametri:
 * 							   |	{@link Object} properties, properties    (OBB)
 * 							   |	{@link ol.geom.Geometry} geom, geometria della feature già convertita nel formato accettato da ol3 e 
 * 							   |						           nella projection "ESPG:4326" (OPT)
 * 				   		       |
 * 		"removeLayer"  		   | Rimuove dalla mappa l'instanza del layer fornito in ingresso.
 *   						   |  Parametri:
 *   						   |  	{@link ol.layer.Vector} layer, layer da rimuovere		(OBB)
 * 				   		       |
 * 		 "addLayer"			   | Crea ed aggiunge alla mappa un layer vettoriale dove il contenuto dati è fornito in ingresso alla funzione.
 * 				 			   | La lista di elementi "aaData" deve essere nel fomato GeoJson quindi una @link FeatureCollection.
 * 				 			   | Il widget ha un sistema interno di gestione del loading.
 * 				 			   |  Parametri:
 *					 	 	   |	{@link FeatureCollection} aaData, lista di elementi nel fomato GeoJson		(OBB)
 * 					 		   | 	{@Link ol.style.Style} style, Stile delle feature							(OBB)
 *						 	   |    {@link String} layerName, codice univoco del layer  						(OPT)
 *					 		   |  Ritorna:
 * 					 		   |  	{@link ol.layer.Vector}, instanza del vector creato.
 *   						   |
 */
BootApplication.directive('widgetRicercaOnMap',
	["configuration", "olMapsInst", "olBuildService", "olInteractionService", "olFormatService", "log", "$compile", "$translate", "$http", "$rootScope", "TemplateStyles", "olFeatureService",
    function(appConstants, olMapsInst, olBuildService, olInteractionService, olFormatService, log, $compile, $translate, $http, $rootScope, TemplateStyles, olFeatureService) {
			
	return {
        restrict: 'EA',
        template: ngTemplateRicercaOnMap,
        replace: true,
		scope: {
			api : "=",
			config: "=",
			parentScope: "="
	    },
	    link: function (scope, element, attrs) {
	    	   
  /** ======================================================================================== **/
  /**  											UTILITY TOOL								   **/
  /** ======================================================================================== **/
	    	   
	    	/**
			 * Svuota la mappa da eventuali geometrie disegnate e chiude l'eventuale popup aperta.
			 */
	    	scope.resetWidget = function () {
	    		/** Quando inizio a disegnare svuoto il layer dall'ultima feature disegnata. */
				if( scope.vector != null ) scope.vector.getSource().clear(true);
				
				/** hide popup **/
				scope.closePopup();
				
		        /** refresh mappa **/
  				scope.onResize();
	    	}// close resetWidget
	    	
	    	/**
	    	 * Nasconde la popup se aperta, e deseleziona l'eventuale feature selezionata.
	    	 */
	    	scope.closePopup = function() {
	    		/**
	    		 * Set the position for this overlay. If the position is undefined the overlay is hidden.
	    		 */
	    		scope.overlay.setPosition(undefined);
	    		
	    		/** Elimino le feature eventualmente selezionate. */
  				if( scope.highlightsInteraction != null ) scope.highlightsInteraction.getFeatures().clear(true);
  				/** Elimino le feature eventualmente selezionate. */
  				if( scope.selectInteraction != null ) 	  scope.selectInteraction.getFeatures().clear(true);
	    		
	    		return false;
	    	}// close closePopup
	    	
	    	/**
	    	 * Apre la popup della feature selezionata.
	    	 * 
	    	 * @param {@link ol.Feature} fea, A vector object for geographic features with a geometry and other attribute properties
	    	 * @param {@link Boolean} centerOn, True o false a seconda se si vuole zoomare all'estensione del layer dopo l'apertura della popup
	    	 */
	    	scope.openPopup = function (fea, centerOn){
	    		/* e stata selezionata una feature nascondo la popup corrente */
	    		scope.overlay.setPosition(undefined);
	    		/* svuoto la variabile di scope dove mantengo l'informazione dell'anagrafica della feature selezionata */
	    		scope.properties = null;
	    		
	    		/* 
				 * Dopodicchè in base alla geometria, cerco di determinare un punto centrale alla stessa da assegnare all'overlay
				 * come la posizione in cui se deve aprire.
				 * Se non rieso a determinarle usa la coppia di coordinate derivate dal click sulla mappa.
				 */ 
				var coordinates = olFeatureService.getCentroidGeometry(fea.getGeometry());
				
				/* recupero le proprieta della feature, le aggiungo allo scope di modo che siano accesibili dal template della popup **/
				scope.properties = fea.getProperties();
				
				/* creo una copia del template di modo da non modificare l'originale */
				var template = angular.copy(scope.config.templatePopup);
                  
				/* scrivo all'interno del template della popup, il template dell'anagrafica */
				scope.element.find(".popup-content").html(template);
				
				/* compile code angular */
				$compile(scope.element)(scope);
				
				/* mostro la popup settando la coordinate dela posizione dell'overlay */
				scope.overlay.setPosition(coordinates);
				if(centerOn) {
					/* Ottenere l'estensione delle features. */
			    	var extent = fea.getGeometry();
			    		
			    	/* Adatta la forma geometrica data o misura in base alla data dimensione mappa e di confine. **/
			    	scope.olMapTb.getView().fit(extent, scope.olMapTb.getSize()[0] == 0 ? [500, 500] : scope.olMapTb.getSize(), {"maxZoom": 16});
				}
	    	}// close openPopup
	    	
	    	/**
	    	 * Aggiorna la posizione e le proprietà della popup, verificando che ci sia una popup visibile e che si riferisca alla feature
	    	 * con id "properties.id".
	    	 * 
	    	 * @param {@link Object} properties, properties 
	    	 * @param {@link ol.geom.Geometry} position, geometria della feature 
	    	 */
	    	scope.refreshPopup = function (properties, position){
	    		
	    		if(scope.overlay.getPosition() != undefined && scope.properties.id  == properties.id){
					/* Controllo se la position è stata fornita */
					if(angular.isDefined(position) && position != null) {
						/* 
						 * Dopodicchè in base alla geometria, cerco di determinare un punto centrale alla stessa da assegnare all'overlay
						 * come la posizione in cui se deve aprire.
						 * Se non rieso a determinarle usa la coppia di coordinate derivate dal click sulla mappa.
						 */ 
						var coordinates = olFeatureService.getCentroidGeometry(position);
						/* posiziono la popup settando le coordinate della posizione dell'overlay */
						scope.overlay.setPosition(coordinates);
					}
					
	    			/* aggiorno le properties della popup */
	    			scope.properties = properties;
	    		}
	    	}// close refreshPopup
	    	
  /** ======================================================================================== **/
  /**  											RESIZE MAPPA								   **/
  /** ======================================================================================== **/
	    	   
	    	/**
	    	 * Gestisce resize mappa
	    	 */
	    	scope.onResize = function (){
	    		scope.olMapTb.updateSize();
	    		scope.olMapTb.render();
	    	   	var el = scope.olMapTb.getTargetElement().getAttribute("id");
				if(el != undefined){
					var padre = angular.element("#"+el).closest(".tab-content");
					if(padre){
						var diff = padre.scrollTop() - padre.offset().top + angular.element("#"+el).offset().top;
						angular.element("#"+el).closest(".tab-content").scrollTop(diff);
					}
				}
	    	}
	    	/**
	    	 * Gestione evento di resize del DIV della direttiva
	    	 */
	    	scope.$watch( function() {
	    		scope.__width = element.width();
	        });
	    	scope.$watch('__width', function( newHeight, oldHeight ) {
	    		scope.onResize();
	    	});
	    	/**
	    	 * Gestione evento di resize della finestra del browser
	    	 */
	    	$(window).bind('resize', function(){
	    		scope.onResize();
	    	});
	    	
  /** ======================================================================================== **/
  /**  										 FUNCTION PUBBLICHE								   **/
  /** ======================================================================================== **/
	    	
	    	scope.api = {
	    		/**
	    		 * Forza il ricalcolo della dimensione della mappa. Questo dovrebbe essere chiamato quando il codice di terze parti cambia la 
	    		 * dimensione della finestra in cui è disegnata la mappa.
	    		 */
	    		updateSize : function () {
	    			/**
	    			 * Forza il ricalcolo della dimensione della mappa
	    			 */
		    	   	scope.onResize();
		    	},
		    	/**
		    	 * Ripete la ricerca con i nuovi filtri importati, in seguito alla ricerca sposta la vista della mappa
		    	 * ad un estensione ed uno zoom tale che i marker siano visibili.
		    	 */
		  		refresh: function () {
		  			/**
					 * Svuota la mappa da eventuali geometrie disegnate e chiude l'eventuale popup aperta.
					 */
			    	scope.resetWidget();
			    	/* loader feature as start .. */
			    	scope.endLoader = false;
					/**
					 * Refreshes the source and finally dispatches a 'change' event.
					 */
					scope.vector.getSource().refresh();

					/**
	    			 * Forza il ricalcolo della dimensione della mappa
	    			 */
		    	   	scope.onResize();
		  		},
		  		removeLayer: function (_layer) {
		  			scope.olMapTb.removeLayer(_layer);
		  			scope.endLoader = true;
		  		},
		  		/**Funzion addfeature*/
		  		addLayer: function (_response, _functionStyle, _applyStyleToFeature) {
		  			_vectorSource = new ol.source.Vector();
		  			_features     =  olFormatService.olReadFeaturesToGeoJson(_response.data.aaData
		  					                                                ,scope.olMapTb.getOptions().getSourceProjection()
		  					                                                ,scope.olMapTb.getOptions().getDestProjection()
		  					                                                );
		  			if(_applyStyleToFeature){
		  				_layer        = new ol.layer.Vector({source: _vectorSource
		  					                                ,name: scope.config.layerName	
		  				                                   });
		  				
		  			}else{
		  				_layer = new ol.layer.Vector({source: _vectorSource
		  					                         ,style : _functionStyle
		  					                         ,name: scope.config.layerName	
		  				                             });
		  			}
		  			if(_applyStyleToFeature){
                        for(idx = 0 ; idx < _features.length ; idx++){
                        	_features[idx].setStyle(_functionStyle(_features[idx]));
                        }
		  			}
		  		    _vectorSource.addFeatures(_features);
		    		/** add layer alla mappa **/
		    		scope.olMapTb.addLayer(_layer);
		  			scope.endLoader = true;
		  			
		    		//var highlightsInteraction = olInteractionService.createHighlightsFeatureInteraction(null, _layer, true, _functionStyle);
		    		//var selectInteraction     = olInteractionService.createSelectFeatureInteraction    (null, _layer, true, _functionStyle);

		    		//scope.olMapTb.addInteraction(highlightsInteraction);
		    		//scope.olMapTb.addInteraction(selectInteraction    );
		    		
		    		/**
		    		 * Listner in select event
		    		 */
		    		//selectInteraction.on('select', function(e) {
		    		//	if(e.selected.length != 0){
		    		//		var fea = e.selected[0];
		    		//		scope.openPopup(fea, false);
		            //    }
		    		//});
		  			scope.onResize();
		  			
		  			return _layer;
		  		},
		  		/**
		  		 * NOTA:
		  		 *  La seguente funzione è specifica per quando la direttiva è configurata perchè il caricamento delle feature avvenga
		  		 *  utilizzando la strategy "ALL".
		  		 * ----------------------------------------------------------------------------------------------------------------------
		  		 * 
		  		 * Evidenzia la fetaure le cui proprietà hanno la chiave di attributo fornita al valore dato,
		  		 * se la feature viene individuata ne apre la popup.
		  		 * 
		  		 * @param {@link String} key, nome dell'attributo
		  		 * @parma {@link Mixed} value, valore dell'attributo
		  		 */
		  		selectFeature: function (key, value){
			  		/**
			  		 * Attendo che le feature siano recuperate e disegnate in mappa
			  		 */
		  			var unregisterWatch = scope.$watch('endLoader', function(newValue, oldValue) {
			  	    	
		  				if(newValue){
		  					/* Elimino le feature eventualmente selezionate. */
		  					if( scope.highlightsInteraction != null ) scope.highlightsInteraction.getFeatures().clear(true);
		  					/* Elimino le feature eventualmente selezionate. */
		  					if( scope.selectInteraction != null ) 	  scope.selectInteraction.getFeatures().clear(true);
			  				
			  				/* Restituisce un array di feature che hanno la chiave di attributo data impostato al valore dato. */
							var foundFea = scope.vector.getSource().getFeaturesByAttribute(key, value);
							
							/* Recupero l'elenco di feature già selezionate */
				  			var features = scope.selectInteraction.getFeatures();
					  		
					  		/* controllo se è stata trovata una feature con attributo uguale a quello fornito, se si le rimuovo */
							if(foundFea != null)
								for(var i = 0; i<foundFea.length; i++) { 
									/* la aggiungo alla lista delle feature selezionate */
									features.push(foundFea[i]);
									/* Apre la popup della feature selezionata. */
									scope.openPopup(foundFea[i], true);
								}
								
							/* Once the feedback has been displayed, there's no more need to watch the change in the model value. */
							unregisterWatch();
			  	    	}// close true
		  			});
		  		},// close selectFeature
		  		/**
		  		 * NOTA:
		  		 *  La seguente funzione è specifica per quando la direttiva è configurata perchè il caricamento delle feature avvenga
		  		 *  utilizzando la strategy "BBOX".
		  		 * ----------------------------------------------------------------------------------------------------------------------
		  		 * 
		  		 * Evidenzia la fetaure le cui proprietà hanno la chiave di attributo fornita impostato al valore dato,
		  		 * se la feature viene individuata ne apre la popup.
		  		 * 
		  		 * @param {@link String} key, nome dell'attributo
		  		 * @param {@link Mixed} value, valore dell'attributo
		  		 * @param {@link String} geomAsWkt, rappresentazione WKT della geometria della feature da evidenziare
		  		 */
		  		selectFeatureBbox: function (key, value, geomAsWkt){
		  			
		  			/* loader feature as start .. */
					scope.endLoader = false;
					
					/* Ritorna la geometry dalla stringa WKT. */
					var envelope = olFormatService.olReadGeometryToWkt(geomAsWkt, scope.olMapTb.getOptions().getSourceProjection(), scope.olMapTb.getOptions().getDestProjection());

			  		/**
			  		 * Attendo che le feature siano recuperate e disegnate in mappa
			  		 */
		  			var unregisterWatch = scope.$watch('endLoader', function(newValue, oldValue) {
			  	    	
		  				if(newValue){
		  					/* Elimino le feature eventualmente in hover. */
		  					if( scope.highlightsInteraction != null ) scope.highlightsInteraction.getFeatures().clear(true);
		  					/* Elimino le feature eventualmente selezionate. */
		  					if( scope.selectInteraction != null ) 	  scope.selectInteraction.getFeatures().clear(true);
			  				
			  				/* Restituisce un array di feature che hanno la chiave di attributo fornita impostata al valore dato. */
							var foundFea = scope.vector.getSource().getFeaturesByAttribute(key, value);
							
							/* Recupero l'elenco di feature già selezionate */
				  			var features = scope.selectInteraction.getFeatures();
					  		
					  		/* controllo se è stata trovata una feature con attributo uguale a quello fornito, se si le rimuovo */
							if(foundFea != null)
								for(var i = 0; i<foundFea.length; i++) { 
									/* la aggiungo alla lista delle feature selezionate */
									features.push(foundFea[i]);
									/* Apre la popup della feature selezionata. */
									scope.openPopup(foundFea[i], true);
								}
								
							/* Once the feedback has been displayed, there's no more need to watch the change in the model value. */
							unregisterWatch();
			  	    	}// close true
		  			});
		  			
		  			/* Sposto la finestra di visualizzazione della mappa all'estensione della geometria */
			    	scope.olMapTb.getView().fit(envelope, scope.olMapTb.getSize(), {"maxZoom": 16});
			    	
			    	/*
					 * Forzo il refresh del layer di modo che la chiamata http venga ripetuta caricando cosi le feature presenti
					 * in questa porzione di mappa.
					 */
					scope.vector.getSource().refresh();
		  		},// close selectFeatureBbox
		    	/**
		    	 * Ritorna le proprietà della feature con id "properties.id" uguale al valore fornito in ingresso alla funzione,
		    	 * altrimenti ritorna null.
		    	 * 
		    	 * @param {@link String} id, identificativo elemento 
		    	 */
		    	getPropertiesByFeatureId: function (id) {
		    		/* Controllo se al layer è stata aggiunta la feature originale */
		  			var feature = scope.vector.getSource().getFeatureById(id);
		  			
		  			if(angular.isDefined(feature) && feature != null) 
		  				return feature.getProperties();
		  			else 
		  				return null;
		    	},// close getPropertiesByFeatureId
		    	/**
		    	 * Aggiorna le proprietà e/o la geometria della feature e della popup con id "properties.id".
		    	 * 
		    	 * @param {@link Object} properties, properties (OBB)
		    	 * @param {@link ol.geom.Geometry} geom, geometria della feature già convertita nel formato accettato da ol3 e nella projection 
		    	 * 										 "ESPG:4326" (OPT)
		    	 */
		    	updatePropertiesByFeatureId: function (properties, geom){
		    		
		  			/* Controllo se al layer è stata aggiunta la feature originale */
		  			var feature = scope.vector.getSource().getFeatureById(properties.id + "");
		  			/* Controllo se al layer è stata aggiunta eventuale copia, se si devo rimuoverla */
		  			var featureClone = scope.vector.getSource().getFeatureById(properties.id + "_copy");
		  			
		  			/* Rimuovo la copia della feature dal source. */
		  			if(angular.isDefined(featureClone) && featureClone != null){
		  				/* Rimuovo la copia della feature dal source, perchè devo riaggiungerla con i dati relatime aggiornati */
		  				scope.vector.getSource().removeFeature(featureClone);
		  			}
		  			
		  			if(angular.isDefined(feature) && feature != null) {
		  				/* creo una copia della feature */
		  				var nw = feature.clone();

		  				/* Rimuovo l'originale della feature dal source, perchè devo riaggiungerla con i dati relatime aggiornati */
		  				scope.vector.getSource().removeFeature(feature);
		  				
		  				/* set properties aggiornate */
		  				nw.setProperties(properties);
		  				
		  				/* Controllo se da aggiornare c'è anche la geometria, se si prima d'impostarla devo convertire la projection in quella della mappa */
		  				if(angular.isDefined(geom) && geom != null && geom instanceof ol.geom.Geometry) 
		  					/* trasform projection and set geometry feature */
		  					nw.setGeometry(geom.transform($scope.olMapTb.getOptions().getSourceProjection(), $scope.olMapTb.getOptions().getDestProjection()));
		  				
		  				/* la reinserisco in mappa */
		  				scope.vector.getSource().addFeature(nw);

		  				/*
		  				 * Aggiorna la posizione e le proprietà della popup, verificando che ci sia una popup visibile e che si riferisca alla feature con id "properties.id".
		  				 */
		  				scope.refreshPopup(properties, (angular.isDefined(geom) && geom != null && geom instanceof ol.geom.Geometry) ? nw.getGeometry() : null);
		  			}//
		  		},// close updatePropertiesByFeatureId
		  		/**
		  		 * Rimuove dalla mappa l'instanza del layer fornito in ingresso.
		  		 * 
		  		 * @param {@link ol.layer.Vector} layer
		  		 */
		  		removeLayer: function (layer) {
		  			/* remove layer */
		  			scope.olMapTb.removeLayer(layer);
		  			/* loader feature as end .. */
		  			scope.endLoader = true;
		  		},// close 
		  		/**
		  		 * Crea ed aggiunge alla mappa un layer vettoriale dove il contenuto dati è fornito in ingresso alla funzione.
		  		 * La lista di elementi "aaData" deve essere nel fomato GeoJson quindi una @link FeatureCollection.
		  		 * Il widget ha un sistema interno di gestione del loading.
		  		 * 
		  		 * @param {@link FeatureCollection} aaData, lista di elementi nel fomato GeoJson
		  		 * @param {@Link ol.style.Style} style, Stile delle feature
		  		 * @param {@link String} layerName, codice univoco del layer  (OPT)
		  		 * 
		  		 * @return {@link ol.layer.Vector}
		  		 */
		  		addLayer: function(response, style, layerName) {
		  			
		  			/* loading ... */
					scope.isLoading = true;
					/* loader feature as start .. */
					scope.endLoader = false;
					
		  			var source = new ol.source.Vector({ 
							features: [],
		    				/** strategy one-off che carica tutte le funzioni contemporaneamente. **/
							strategy: ol.loadingstrategy.all,
							/** **/
							wrapX: false
		    			});
		  			
		  			/*
		  			 * Ritorna le features dalla stringa GeoJson.
		  			 */
		  			var features = 
						olFormatService.olReadFeaturesToGeoJson(response.data.aaData, scope.olMapTb.getOptions().getSourceProjection(), scope.olMapTb.getOptions().getDestProjection());
					
					/* add features */
					source.addFeatures(features);
		  			
					var vector = new ol.layer.Vector({
							/** **/
							source: source,
							/** The feature style. ol.style.Style **/
							style: style,
							/** Nome del layer **/
							name: layerName,
							/** Visibility **/
							visible: true
						});	
					 
		    		/* add layer alla mappa **/
		    		scope.olMapTb.addLayer(vector);
		    		
		    		/* spengo loading ... */
					scope.isLoading = false;
					/* loader feature as end .. */
					scope.endLoader = true;
					/*
	    			 * Forza il ricalcolo della dimensione della mappa
	    			 */
		  			scope.onResize();
		  			
		  			return vector;
		  		},// close addLayer
		  		/**
		    	 * Aggiorna le proprietà della popup, verificando che ci sia una popup visibile e che si riferisca alla feature
		    	 * con id "properties.id".
		    	 * 
		    	 * @param {@link Object} properties, properties 
		    	 */
		    	refreshPopup: function (properties){
		    		/**
			    	 */
		    		scope.refreshPopup(properties);
		    	}// close refreshPopup
	    	}// close api
	    },
        controller: ["$scope", function($scope) {
        	
        	/* Aggiungo allo scope della direttiva il language impostato nel $rootScope */
        	$scope.language = $rootScope.language;
        	
   /** ======================================================================================== **/
   /**  											MAPPA										**/
   /** ======================================================================================== **/
        	        	        		
        	/**
        	 * Inizializzo l'oggetto contenente le impostazioni di configurazione della mappa.
        	 */
        	$scope.olMapConfiguration = {
        		/* La projection con la quale l'entità sono gestite. **/
        		sourceProjection: appConstants.getParam("Constants").SOURCE_PROJECTION,
        		/* La projection con la quale l'entità sono disegnate in mappa. **/
        		destProjection: appConstants.getParam("Constants").DEST_PROJECTION,
        		/* Identificativo dell'elemento (div) in cui disegnare la mappa **/
        		targetMap: "mapRisRc",
        		/* Opzione per mostrare tutti gli attributi associati con le fonti di livello nella mappa. **/
            	enableAttribution: true,
        		/* Un controllo per disegnare la geometria di punti, linestring e poligoni sulla mappa **/
        		enableDrawGeometry: false,
        		/* Opzione che fornisce un pulsante per attivare o disattivare la mappa in modalità a schermo intero. **/
				enableFullscreen: false,
				/* Un controllo per mostrare le coordinate 2D del cursore del mouse. **/
				enableMousePosition: true,
				/* Un controllo pulsante che, se premuto, cambia la visualizzazione della mappa in misura specifica. **/
				enableZoomToExtent: true,
				/* 
				 * Il controllo LayerSwitcher visualizza una tabella di contenuti per la mappa. In questo modo l'utente può per i layer di Base passare da un
				 * BaseLayers ad un'altro.
				 * Mentre, per i layer non di base, mostrare o nascondere i livelli e consentirne il riordino. 
				 * Per impostazione predefinita, lo switcher è mostrato minimizzato sul bordo destro della mappa, l'utente può espandere cliccando sulla maniglia.
				 */
				enableLayerSwitcher: false,
				/*
				 * Identificativo dell'elemento da visualizzare a schermo intero. Quando non è previsto, sarà l'elemento che contiene la mappa viewport 
				 * ad essere visualizzato a schermo intero. 
				 */
				fullscreenSourceId: null,
				/*
				 * Identificativo dell'elemento HTML in cui openalyer inserirà i controlli che la libreria mette a disposizione. 
				 * (Es: zoomExtent, fuulscreen, 3D)
				 */
				controlTargetId: null,
				/*
				 * Determina se aggiungere ai controlli di Openlayer oltre l'icona, già prevista, anche una label testuale.
				 * (Es: zoomExtent, fullscreen, 3D, geoLocate, measureLength, measureArea, measureAzimut )
				 */
				advancedControlHasLabel: false
        	};
        	
        	/** 
        	 * Costruisco la mappa dalle configurazioni fornite di cui sopra.
        	 */
        	var olMapTb = olBuildService.buildMapToOptions($scope.olMapConfiguration);
        	/**
        	 * Add instance to scope
        	 */
        	$scope.olMapTb = olMapTb.getMap2DInstance();
    		$scope.extent  = null;
        	$scope.olMapTb.on("moveend", function() {
        		  if($scope.olMapTb
        		  && $scope.olMapTb.getView()
        		  && $scope.olMapTb.getSize()
        		  ){
        		      $scope.extent = ol.proj.transformExtent($scope.olMapTb.getView().calculateExtent($scope.olMapTb.getSize())
        		    		                                 ,$scope.olMapConfiguration.destProjection
        		    		                                 ,$scope.olMapConfiguration.sourceProjection
        		    		                                 );
        			  $scope.vector.getSource().clear(true);
        		  }
        		});
        	
  /** ======================================================================================== **/
  /**  										LAYER RESULT TABLE								   **/
  /** ======================================================================================== **/
    		        		
    		/**
    		 * Ritorna il layer vector appositamente configurato per la visualizzazione su mappa delle feature.
    		 */
    		var source = new ol.source.Vector({ 
					features: [],
					/**
					 * The loader function used to load features, from a remote source for example. Note that the source will create and use 
					 * an XHR feature loader when url is set.
					 * 
					 * The function is responsible for loading the features and adding them to the source.
					 */
					loader: function(extent, resolution, projection) {
						
						/* loading ... */
						$scope.isLoading = true;
						/* loader feature as start .. */
						$scope.endLoader = false;
						
						/* Filtri di ricerca da mandare al servizio che ritorna le feature */
						var params = $scope.config.requestData;
							/* linguaggio corrente **/
							params.locale = $scope.language;
							/* disattivo la paginazione */
							params.paginate = false;
							
						if($scope.extent != null
						&& $scope.extent.length == 4
						&& isNaN($scope.extent[0]) == false
						&& isNaN($scope.extent[1]) == false
						&& isNaN($scope.extent[2]) == false
						&& isNaN($scope.extent[3]) == false
						){
							_bbox = "POLYGON("
								   + "(" + $scope.extent[0] + " " + $scope.extent[1]
								   + "," + $scope.extent[0] + " " + $scope.extent[3]
								   + "," + $scope.extent[2] + " " + $scope.extent[3]
								   + "," + $scope.extent[2] + " " + $scope.extent[1]
							       + "," + $scope.extent[0] + " " + $scope.extent[1]
							       + ")"
								   + ")"
								   ;
							params.bbox = {wkt: _bbox};
							
						}else{
							return ;
						}
						/**
						 * Controllo la strategy di caricamento da usare per il layer vector.
						 */
						if($scope.config.enableStrategyBbox){
							/*
							 * Get an ol.Extent rappresentante l'area della mappa caricata.
							 */
							var envelope = olFormatService.olProjTransformExtent(extent, $scope.olMapTb.getOptions().getDestProjection(), $scope.olMapTb.getOptions().getSourceProjection());
							/*
							 * Set come filtro di ricerca l'estensione della view della mappa.
							 */
							params.envelope = {
								/** the minimum x-coordinate */
								minx: envelope[0],
								/** the maximum x-coordinate */
								maxx: envelope[2],
								/** the minimum y-coordinate */
								miny: envelope[1],
								/** the maximum y-coordinate */
								maxy: envelope[3]
							}
						}//
							  
						/*  request object */
						$http({
							method: 'POST',
							url: appConstants.getParam("Constants").ContextPath + $scope.config.url,
							data: angular.toJson(params),
							responseType: "json",
							cache: false
						})
						.then(function(response) {
							if(response.data != null && response.data.success == true && response.data.aaData != null && response.data.aaData.features.length != 0){
								
								// dataProjection will be read from document
								var features = 
									olFormatService.olReadFeaturesToGeoJson(response.data.aaData, $scope.olMapTb.getOptions().getSourceProjection(), $scope.olMapTb.getOptions().getDestProjection());
								
								/* add features */
								source.addFeatures(features);
								
								/*
								 * Controllo se la strategy "BBOX" di caricamento è abilitata, se si non centro la mappa sugli elementi.
								 */
								if(!$scope.config.enableStrategyBbox || ($scope.config.enableStrategyBbox && $scope.fitExtentOnce)){
									/**
						    		 * Valorizzo all'inverso l'opzione per cui solo la prima volta deve zoomare all'estensione delle feature risultate dalla ricerca,
						    		 * di modo da non farlo più.
						    		 */
									$scope.fitExtentOnce = false;
									
									/* Ottenere l'estensione delle features. */
							    	var extent = source.getExtent();
							    		
							    	/* Adatta la forma geometrica data o misura in base alla data dimensione mappa e di confine. */
							    	if($scope.config.treeConfig)
							    		if(false !== $scope.config.treeConfig.refreshView)
							    			$scope.olMapTb.getView().fit(extent, $scope.olMapTb.getSize(), {"maxZoom": 14});
								}
							}else
							   /* add features vuota */
							   source.addFeatures([]);
							
							/* spengo loading ... */
							$scope.isLoading = false;
							/* loader feature as end .. */
							$scope.endLoader = true;
						}, 
						function errorCallback(response) {
							/* add features vuota */
							source.addFeatures([]);
							/* spengo loading ... */
							$scope.isLoading = false;
							/* loader feature as end .. */
							$scope.endLoader = true;
							//
							log.error("WidgetRicercaOnMap: ricerca entita' ---> " + ((angular.isDefined(response) && response != null ) ? angular.toJson(response) : response));
						});
					},// close loader
    				/** Determina la strategy di caricamento da usare per il layer vector che accoglie le fetaures risultanti dalla ricerca. **/
					strategy: ($scope.config.enableStrategyBbox) ? ol.loadingstrategy.bbox : ol.loadingstrategy.all,
					/** **/
					wrapX: false
    			});
			
			/* */
    		$scope.vector = new ol.layer.Vector({
				/** **/
				source: source,
				/** The feature style. ol.style.Style **/
				style: function (feature){
					return TemplateStyles.findStyleByCodeLayer($scope.olMapTb, feature);
				},
				/** Nome del layer **/
				name: $scope.config.layerName,
				/** Visibility **/
				visible: true
			});	
    		
    		/** add layer alla mappa **/
    		$scope.olMapTb.addLayers([$scope.vector]);

    		/**
			 * Controllo se la strategy di caricamento da usare per il layer vector è la BBox, perchè in tal caso devo mettere
			 * in ascolto la mappa sull'evento "change:resolution".
			 */
			if($scope.config.enableStrategyBbox){
				/**
				 * Siccome la strategy bbox cachezza il layer per extent, accadrebbe che poi per extent già caricate, non rieffettua la chiamata 
				 * quindi la forzo io.
				 */
	    		$scope.olMapTb.getView().on('change:resolution', function(evt){
	    		    source.clear(true);
	    		});
	    		/**
	    		 * Valorizzo l'opzione per cui solo la prima volta deve zoomare all'estensione delle feature risultate dalla ricerca.
	    		 */
	        	$scope.fitExtentOnce = true;
			}//
			
 /** ======================================================================================== **/
 /**  								 		POPUP FEATURE									  **/
 /** ======================================================================================== **/
    		 
    		/* popup template */
    		$scope.element = angular.element(
    							"<div class='ol-popup'>" +
    								"<span class='ol-popup-closer' ng-click='closePopup()'></span>" +
					 			 	"<div class='popup-content' ng-cloak translate-cloak ></div>" +
					 			"</div>" );
    		
    		/**
    		 * Un elemento da visualizzare sulla mappa e collegata a una singola posizione sulla mappa. 
    		 * Come ol.control.Control, overlay sono i widget visibili. A differenza dei controlli, non sono in una posizione fissa sullo schermo, 
    		 * ma sono legati a coordinate geografiche, così lo scorrimento della mappa muoverà l'overlay ma non un controllo.
    		 */
    		$scope.overlay = new ol.Overlay({
    			/**
    			 * The overlay element.
    			 */
    	        element: $scope.element[0],
    	        /**
    	         * Offsets in pixels used when positioning the overlay. The fist element in the array is the horizontal offset. 
    	         * A positive value shifts the overlay right. The second element in the array is the vertical offset. 
    	         * A positive value shifts the overlay down. Default is [0, 0].
    	         */
//    	        offset: 
    	        /**
    	         * Defines how the overlay is actually positioned with respect to its position property. 
    	         * Possible values are 'bottom-left', 'bottom-center', 'bottom-right', 'center-left', 'center-center', 'center-right', 'top-left', 
    	         * 'top-center', and 'top-right'. Default is 'top-left'.
    	         */
    	        positioning: 'top-center',
    	        /**
    	         * Whether event propagation to the map viewport should be stopped. Default is true. 
    	         * If true the overlay is placed in the same container as that of the controls (CSS class name ol-overlaycontainer-stopevent); 
    	         * if false it is placed in the container with CSS class name ol-overlaycontainer.
    	         */
    	        stopEvent: true,
    	        /**
    	         * if set to true the map is panned when calling setPosition, so that the overlay is entirely visible in the current viewport. 
    	         * The default is false.
    	         */
    	        autoPan: true,
    	        /**
    	         * The options used to create a ol.animation.pan animation. This animation is only used when autoPan is enabled. 
    	         * By default the default options for ol.animation.pan are used. If set to null the panning is not animated.
    	         */
    	        autoPanAnimation: {
    	           /** The duration of the animation in milliseconds. Default is 1000. **/
    	           duration: 250,
    	           /** Maintain a constant speed over time.**/
    	           easing: ol.easing.linear
    	        },
    	        /**
    	         * The margin (in pixels) between the overlay and the borders of the map when autopanning. The default is 20.
    	         */
    	        autoPanMargin: 20
    	     });
    		
    		/** Add the given overlay to the map.**/
    		$scope.olMapTb.addOverlay($scope.overlay);
   
    		 
 /** ======================================================================================== **/
 /**  								INTERACTION HIGHLIGHTS FEATURE							  **/
 /** ======================================================================================== **/
    		    		    	
    		/*
    		 * Style for the selected features
    		 */
    		var styleHighlight = function (feature){
    			return TemplateStyles.findStyleHighlightSelectByCodeLayer($scope.olMapTb, feature);
    		};
    		/**
    		 * Ritorna lo strumento che gestisce l'highlights dei dati vettoriali.
    		 */
    		$scope.highlightsInteraction = olInteractionService.createHighlightsFeatureInteraction(null, $scope.vector, true, styleHighlight);
    		/**
    		 * Add the given interaction to the map.
    		 */
    		$scope.olMapTb.addInteraction($scope.highlightsInteraction);
    		
	    		    		
 /** ======================================================================================== **/
 /**  								 INTERACTION SELECT FEATURE								  **/
 /** ======================================================================================== **/
    		    	
    		/*
    		 * Style for the selected features
    		 */
    		var styleSelect = function (feature){
    			return TemplateStyles.findStyleHighlightSelectByCodeLayer($scope.olMapTb, feature);
    		};
    		/**
    		 * Ritorna lo strumento che gestisce la selezione dei dati vettoriali.
    		 */
    		$scope.selectInteraction = olInteractionService.createSelectFeatureInteraction(null, $scope.vector, true, styleSelect);
    		/**
    		 * Add the given interaction to the map.
    		 */
    		$scope.olMapTb.addInteraction($scope.selectInteraction);
    		
    		/**
    		 * Listner in select event
    		 */
    		$scope.selectInteraction.on('select', function(e) {
    			if(e.selected.length != 0){
    				/* recupero la feature selezionata */
    				var fea = e.selected[0];
    				/* Apre la popup della feature selezionata. */
    				if(angular.isDefined(fea) && fea != null) $scope.openPopup(fea, false);
    				/* */
    				$scope.selectInteraction.getFeatures().clear(true);
                }
    		});
    		
 /** ======================================================================================== **/
 /**  										CONTROLLI										  **/
 /** ======================================================================================== **/
    		    
    		/** 
             * Aggiungo il controllo di openlayer che, se premuto, visualizza una tabella di contenuti per la mappa. In questo modo l'utente può per i 
             * layer di Base passare da un BaseLayers ad un'altro.
    		 * Mentre, per i layer non di base, mostrare o nascondere i livelli e consentirne il riordino. 
    		 * Per impostazione predefinita, lo switcher è mostrato minimizzato sul bordo destro della mappa, l'utente può espandere cliccando sulla 
    		 * maniglia.
             */
            var layerSwitcher = new mycontrol.LayerSwitcher({
    			/* Text label to use for the button tip. Default is "Legend" **/
    			tipLabel: $translate.instant('OpenLayers.olSwitcher.tipLabel'),
    			/* Testo dell'etichetta da utilizzare per identificare i layer di base. Il valore predefinito è 'Base Layer'.*/
    			baseLabel: $translate.instant('OpenLayers.olSwitcher.baseLabel'),
    			/* Testo dell'etichetta da utilizzare per identificare i layer non di base. Il valore predefinito è 'Overlay'. */
    			overlayLabel: $translate.instant('OpenLayers.olSwitcher.overlayLabel'),
    			/*  Se nel layer panel dovrebbero essere gestiti anche i layer non di base. Predefinito true. */
    			hasOverlay: false
    		});
            $scope.olMapTb.addControl(layerSwitcher);
       }]
	}
}]);
